//
//  ASRManager_JTHS.m
//  ASR_Demo
//
//  Created by marco on 16/3/11.
//  Copyright © 2016年 Marco. All rights reserved.
//

#import "ASRManager_JTHS.h"
#import "FileBuf.h"
#import "CommonTool.h"
#import "AccountInfo.h"



//检测、获取能力授权的函数
bool CheckAndUpdataAuth()
{
    /* 检查授权是否正确，必要时会下载云端授权，授权过期后7天内继续有效
     
     更新授权文件有如下两种做法：
     
     1. 在 hci_init() 时将 autoCloudAuth 配置项设为 yes，系统会启动一个后台线程，
     定期检查授权的过期时间， 如果授权过期时间已到，会自动更新授权文件。此为缺省配置。
     
     2. 如果 autoCloudAuth 设为 no，则需要开发者自行在适当的时机通过 hci_check_auth() 来更新授权。
     
     自动更新授权的方式在PC等联网条件不受限的情况下会工作得很好，但对于移动终端应用等对网络条件
     和流量比较敏感的情况来说， 最好由开发者自行决定更新授权的时机，这样可控性更强，例如可以在WiFi
     打开的时候才进行授权更新等等。
     
     开发者可以使用 hci_get_auth_expire_time() 获取当前授权过期时间，当此函数返回错误或者授权过期
     时间已经快到了或者已经过期的时候， 再调用 hci_check_auth()函数到云端下载授权文件。例如，下面
     的示例会在授权过期后检测并下载新的授权。
     */
    
    
    
    int64 nExpireTime;
    int64 nCurTime = (int64)time( NULL );
    HCI_ERR_CODE errCode = hci_get_auth_expire_time( &nExpireTime );
    if( errCode == HCI_ERR_NONE )
    {
        if( nExpireTime < nCurTime )
        {
            // 授权已经过期
            errCode = hci_check_auth();
            if( errCode == HCI_ERR_NONE )
            {
                ASR_LOG( "check auth success\n" );
                return true;
            }
            else
            {
                ASR_LOG( "check auth failed %d\n", errCode );
                return false;
            }
        }
        else
        {
            ASR_LOG( "check auth success\n" );
            return true;
        }
    }
    else if( errCode ==	HCI_ERR_SYS_AUTHFILE_INVALID )
    {
        errCode = hci_check_auth();
        if( errCode == HCI_ERR_NONE )
        {
            ASR_LOG( "check auth success\n" );
            return true;
        }
        else
        {
            ASR_LOG( "check auth failed %d\n", errCode );
            return false;
        }
    }
    else
    {
        ASR_LOG( "check auth failed %d\n", errCode );
        return false;
    }
    return true;
}
//////////////////////////////////////////////////////////////////////////
bool IsCapkeyEnable(const string &capKey)
{
    //获取一下授权中全部可用能力列表
    CAPABILITY_LIST capbility_list;
    HCI_ERR_CODE errCode = hci_get_capability_list( NULL, &capbility_list);
    if (errCode != HCI_ERR_NONE)
    {
        ASR_LOG("hci_get_capability_list failed return %d\n",errCode);
        return false;
    }
    
    //判断传入的capbility_array 中的能力是否在可用能力列表中
    bool is_capkey_enable = false;
    for (size_t capbility_index = 0; capbility_index < capbility_list.uiItemCount; capbility_index++)
    {
        if (capKey == string(capbility_list.pItemList[capbility_index].pszCapKey))
        {
            is_capkey_enable = true;
            break;
        }
    }
    //释放可用能力列表
    hci_free_capability_list(&capbility_list);
    return is_capkey_enable;
}
//////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////

// 输出ASR识别结果
void PrintAsrResult(ASR_RECOG_RESULT &recog_result,ASRSystemType pType)
{
    
    for (int i = 0; i < (int)recog_result.uiResultItemCount; ++i)
    {
        ASR_RECOG_RESULT_ITEM& item = recog_result.psResultItemList[i];
        if( pType == kASRSystemType_ZH )
        {
            
            [[NSNotificationCenter defaultCenter] postNotificationName:g_kASRComplete_ZH object:[NSString stringWithUTF8String:item.pszResult]];
        }
        else if( pType == kASRSystemType_UYGHUR )
        {
            
            [[NSNotificationCenter defaultCenter] postNotificationName:g_kASRComplete_UYGHUR object:[NSString stringWithUTF8String:item.pszResult]];
        }
        
    }
    
    
}

// 上传正确结果，可选步骤，如果进行这一步骤有助于建立用户自适应模型
// 这里只是将识别结果中的第一个候选上传了，正式使用时应该改为用户选择的正确结果
void ConfirmResult( ASR_RECOG_RESULT &recog_result, int session_id )
{
    
    ASR_CONFIRM_ITEM confirmResult;
    if( recog_result.psResultItemList == NULL )
    {
        ASR_LOG( "Recog result is NULL.\n" );
        return;
    }
    confirmResult.pszResult = recog_result.psResultItemList[0].pszResult;
    
    HCI_ERR_CODE errCode = hci_asr_confirm(session_id,&confirmResult);
    if( errCode != HCI_ERR_NONE )
    {
        ASR_LOG( "hci_asr_confirm return (%d:%s)\n", errCode ,hci_get_error_info(errCode));
        return ;
    }
    ASR_LOG( "hci_asr_confirm success\n" );
}

//识别
bool Recog(const string &cap_key, const string& recog_config, const string &audio_file,NSData* pData,ASRSystemType pType)
{
    HCI_ERR_CODE err_code = HCI_ERR_NONE;
    // 载入语音数据文件
    CFileBuf voiceData;
    if( pData )
    {
        if( voiceData.Load(pData) == false )
        {
            ASR_LOG( "Open input voice file %s error!\n",audio_file.c_str());
            return false;
        }
    }
    else
    {
        if( voiceData.Load(audio_file.c_str()) == false )
        {
            ASR_LOG( "Open input voice file %s error!\n",audio_file.c_str());
            return false;
        }
    }
    
    // 启动 ASR Session
    int nSessionId = -1;
    string strSessionConfig = "capkey=" + cap_key;
    //此处也可以传入其他配置，参见开发手册，此处其他配置采用默认值
    ASR_LOG( "hci_asr_session_start config[ %s ]\n",strSessionConfig.c_str() );
    err_code = hci_asr_session_start( strSessionConfig.c_str(), &nSessionId );
    if( err_code != HCI_ERR_NONE )
    {
        ASR_LOG( "hci_asr_session_start return (%d:%s)\n", err_code ,hci_get_error_info(err_code));
        return false;
    }
    ASR_LOG( "hci_asr_session_start success\n" );
    
    // 识别
    ASR_RECOG_RESULT asrResult;
    ASR_LOG( "hci_asr_recog config[ %s ]\n", recog_config.c_str() );
    err_code = hci_asr_recog( nSessionId, voiceData.m_pBuf, voiceData.m_nLen, recog_config.c_str(), NULL, &asrResult );
    
    if( err_code == HCI_ERR_NONE )
    {
        ASR_LOG( "hci_asr_recog success\n" );
        // 输出识别结果
        PrintAsrResult( asrResult ,pType);
        
        //提交确认结果，用户可以选择调用
        //ConfirmResult(asrResult,nSessionId);
        // 释放识别结果
        hci_asr_free_recog_result( &asrResult );
    }
    else
    {
        ASR_LOG( "hci_asr_recog return (%d:%s)\n", err_code ,hci_get_error_info(err_code));
    }
    
    // 终止 ASR Session
    hci_asr_session_stop( nSessionId );
    ASR_LOG( "hci_asr_session_stop\n" );
    return true;
}

//实时识别
bool RealtimeRecog(const string &cap_key,  const string& recog_config, const string &audio_file,NSData* pData ,ASRSystemType pType)
{
    
    HCI_ERR_CODE errCode = HCI_ERR_NONE;
    // 载入音频数据
    CFileBuf voiceData;
    if( pData )
    {
        if( voiceData.Load(pData) == false )
        {
            ASR_LOG( "Open input voice file %s error!\n",audio_file.c_str());
            return false;
        }
    }
    else
    {
        if( voiceData.Load(audio_file.c_str()) == false )
        {
            ASR_LOG( "Open input voice file %s error!\n",audio_file.c_str());
            return false;
        }
    }
    
    
    // 启动 ASR Session
    int nSessionId = -1;
    string strSessionConfig = "realtime=yes,capkey=" + cap_key;
    //此处也可以传入其他配置，参见开发手册，此处其他配置采用默认值
    ASR_LOG( "hci_asr_session_start config[ %s ]\n",strSessionConfig.c_str() );
    errCode = hci_asr_session_start( strSessionConfig.c_str(), &nSessionId );
    if( errCode != HCI_ERR_NONE )
    {
        ASR_LOG( "hci_asr_session_start return (%d:%s)\n", errCode ,hci_get_error_info(errCode));
        return false;
    }
    ASR_LOG( "hci_asr_session_start success\n" );
    
    // 实时识别过程模拟，将待识别音频数据分为多段，逐段传入识别接口
    // 若某次传输音频数据后检测到末端，则跳出循环，终止音频数据传入以准备获取识别结果
    ASR_LOG( "hci_asr_recog config[ %s ]\n", recog_config.c_str() );
    int nPerLen = 3200;
    int nLen = 0;            // 当前已传入的长度
    ASR_RECOG_RESULT asrResult;
    while( nLen + nPerLen < voiceData.m_nLen )
    {
        // 本次要传入的参与识别的数据长度，
        // 剩余的多于nPerLen则传入nPerLen个字节，若不足则传入剩余数据
        int nThisLen = 0;
        if( voiceData.m_nLen - nLen >= nPerLen )
        {
            nThisLen = nPerLen;
        }
        else
        {
            nThisLen = voiceData.m_nLen - nLen;
        }
        
        errCode = hci_asr_recog( nSessionId, voiceData.m_pBuf + nLen, nThisLen, recog_config.c_str(), NULL, &asrResult );
        if (asrResult.uiResultItemCount > 0)
        {
            PrintAsrResult(asrResult ,pType);
            hci_asr_free_recog_result(&asrResult);
        }
        
        //返回状态不是HCI_ERR_ASR_REALTIME_WAITING
        if( errCode != HCI_ERR_ASR_REALTIME_WAITING )
        {
            if( errCode == HCI_ERR_ASR_REALTIME_END )
            {
                // 检测到末端了,跳出循环
                break;
            }
            else
            {
                //失败了，跳到session stop处处理
                ASR_LOG( "hci_asr_recog return (%d:%s)\n", errCode ,hci_get_error_info(errCode));
                break;
            }
        }
        nLen += nThisLen;
    }
    
    // 若未检测到端点，但数据已经传入完毕，则需要告诉引擎数据输入完毕
    // 或者检测到末端了，也需要告诉引擎，获取结果
    if( errCode == HCI_ERR_ASR_REALTIME_WAITING || errCode == HCI_ERR_ASR_REALTIME_END )
    {
        errCode = hci_asr_recog( nSessionId, NULL, 0, NULL, NULL, &asrResult );
    }
    
    if( errCode == HCI_ERR_NONE )
    {
        ASR_LOG( "hci_asr_recog success\n" );
        // 输出识别结果
        PrintAsrResult(asrResult,pType);
        //提交确认结果，用户可以选择调用
        //ConfirmResult(asrResult,nSessionId);
        // 释放识别结果
        hci_asr_free_recog_result(&asrResult);
    }
    else
    {
        ASR_LOG( "hci_asr_recog return (%d:%s)\n", errCode ,hci_get_error_info(errCode));
    }
    
    // 终止 ASR Session
    hci_asr_session_stop(nSessionId);
    ASR_LOG( "hci_asr_session_stop\n" );
    
    return true;
}


string capKey;
HCI_ERR_CODE errCode = HCI_ERR_NONE;
unsigned int grammar_id = -1;
HCI_ERR_CODE err_code = HCI_ERR_NONE;
string recog_config = [g_kASRSampleRateRate_16kb UTF8String];



@implementation ASRManager_JTHS
+(instancetype) sharedManager
{
    static ASRManager_JTHS* instance = nil;
    //    dispatch_once_t token;
    //    dispatch_once(&token, ^{
    //        instance = [ASRManager new];
    //    });
    if( !instance )
    {
        instance = [self new];
        
    }
    return instance;
}

- (int) openSystem:(ASRSystemType)pType samplingRate:(NSString *)pSamplingRate
{
    //捷通的授权帐号信息写在了文件AccountInfo.txt里，使用时请替换从捷通申请的帐号
    //GetAccountInfo给字符串strAccountInfo复制，实际上开发这可以通过如下模式来设置自己的账号
    //string strAccountInfo; = "appKey=***,developerKey=***,cloudUrl=***,";
    string strAccountInfo;
    
    NSString* accountInfoFileName = nil;
    bool bRet = false;
    if( pType == kASRSystemType_ZH )
    {
        accountInfoFileName = @"AccountInfo-cn";
    }
    else if( pType == kASRSystemType_UYGHUR )
    {
        accountInfoFileName = @"AccountInfo-uyghur";
    }
    bRet = GetAccountInfo(accountInfoFileName, @"txt", strAccountInfo);
    if(!bRet)
    {
        ASR_LOG("\nAccount information not found in AccountInfo.txt\n");
        return -1;
    }
    
    bRet = GetCapKey(accountInfoFileName, @"txt", capKey);
    if (!bRet) {
        ASR_LOG("\ncapKey not found in AccountInfo.txt\n");
        return -1;
    }
    
    //默认日志级别为0，调试阶段建议打开日志，级别设置为5，开发者也可以设置其他如日志文件大小，日志文件个数等配置，其中日志路径必须是存在的可读写的路径
    string strLogConfig = string("logLevel=5,logFilePath=") + GetIosDocumentPath();
    //授权文件路径，必须是存在的可读写的路径
    string strAuthPath = string(",authPath=") + GetIosDocumentPath();
    string strConfig = strAccountInfo + strLogConfig + strAuthPath;
    errCode = hci_init(strConfig.c_str());
    if (errCode != HCI_ERR_NONE) {
        ASR_LOG("hci_init return %d\n",errCode);
        return -1;
    }
    ASR_LOG("hci_init success\n");
    
    //检测授权,必要时到云端下载授权。此处需要注意的是，这个函数只是通过检测授权是否过期来判断是否需要进行
    //获取授权操作，如果在开发调试过程中，授权账号中新增了灵云sdk的能力，请到hci_init传入的authPath路径中
    //删除HCI_AUTH文件。否则无法获取新的授权文件，从而无法使用新增的灵云能力。
    if (!CheckAndUpdataAuth())
    {
        hci_release();
        return -1;
    }
    
    //判断capkey是否可用
    if (!IsCapkeyEnable(capKey))
    {
        //如果正确的填写了capkey，但是capkey不可用，可以尝试性的做一次hci_check_auth()操作
        ASR_LOG("capKey cannot be used!\n");
        hci_release();
        return -1;
    }
    ASR_LOG("capKey can be used\n");
    //ASR初始化
    string data_path = [[[NSBundle mainBundle] pathForResource:@"data" ofType:nil] UTF8String];
    string asr_init_config = "";
    asr_init_config += "initcapkeys=" + capKey;                    //建议在initcapkeys中添加需要使用的所有能力以提高第一次识别的效率
    asr_init_config += ",datapath=" + data_path;                  //如果是本地能力，此处指定本地能力依赖资源
    err_code = hci_asr_init(asr_init_config.c_str());
    if (err_code != HCI_ERR_NONE)
    {
        ASR_LOG("hci_asr_init return (%d:%s) \n",err_code,hci_get_error_info(err_code));
        return -1;
    }
    ASR_LOG("hci_asr_init success \n");
    
    //如果为本地语法识别，则需要加载语法
    if (capKey.find("asr.local.grammar") != string::npos)
    {
        //语法文件，JSGF格式
        string grammar_file = GetIosFullResDataPath(@"stock_10001", @"gram");
        string load_config = "isFile=yes,grammartype=jsgf";
        load_config += ",capkey=";
        load_config += capKey;
        err_code = hci_asr_load_grammar(load_config.c_str(),grammar_file.c_str(),&grammar_id);
        if (err_code != HCI_ERR_NONE)
        {
            ASR_LOG("hci_asr_load_grammar return (%d:%s) \n",err_code,hci_get_error_info(err_code));
            hci_asr_release();
            return -1;
        }
        ASR_LOG("hci_asr_load_grammar success \n");
    }
    if (capKey.compare("asr.cloud.grammar") == 0)
    {
        //可用云端语法ID请联系捷通华声
        //grammar_id = 2;
    }
    
    //识别
    //识别配置
    recog_config = [pSamplingRate UTF8String];
    if (grammar_id != -1)
    {
        char chTmp[32] =  {0};
        sprintf(chTmp,"%d",grammar_id);
        recog_config += ",grammartype=id,grammarid=" + string(chTmp);
    }
    
    return 0;
}

- (int) realtimeRecog:(NSData*)pData type:(ASRSystemType)pType
{
    
    //识别音频
    //    string audio_file = GetIosFullResDataPath(@"san_xia_shui_li_16k16", @"pcm");
    
    //非实时识别，支持多线程调用
    //云端语法识别暂时只支持非实时识别，如果进行相关示例了解请打开如下注释
    Recog(capKey,recog_config, "",pData,pType);
    
    //实时识别，支持多线程调用
    //    RealtimeRecog(capKey,recog_config, audio_file,pData);
    
    
    return 0;
}
- (void)closeSystem
{
    //语法卸载
    if ( grammar_id!= -1)
    {
        hci_asr_unload_grammar(grammar_id);
        ASR_LOG("hci_asr_unload_grammar\n");
    }
    
    //ASR反初始化
    hci_asr_release();
    ASR_LOG("hci_asr_release\n");
    
    //**********反初始化sys***********//
    errCode = hci_release();
    ASR_LOG("hci_release return: %d\n",errCode);
}
@end
